import {existsSync, readFileSync, writeFileSync} from 'fs';
import dotProp from 'dot-prop';
import signale from './signale';

type keysType =
  | 'concurrency'
  | 'saveLayout'
  | 'saveLayout.track'
  | 'saveLayout.album'
  | 'saveLayout.artist'
  | 'saveLayout.playlist'
  | 'playlist.resolveFullPath'
  | 'trackNumber'
  | 'fallbackTrack'
  | 'fallbackQuality'
  | 'coverSize'
  | 'coverSize.128'
  | 'coverSize.320'
  | 'coverSize.flac'
  | 'cookies.arl';

type configType = {
  concurrency: number;
  saveLayout: {
    track: string;
    album: string;
    artist: string;
    playlist: string;
  };
  playlist: {
    resolveFullPath: boolean;
  };
  trackNumber: boolean;
  fallbackTrack: boolean;
  fallbackQuality: boolean;
  coverSize: {
    '128': number;
    '320': number;
    flac: number;
  };
  cookies: {
    arl: string;
  };
};

const old_arl =
  '118fe80f967e1883469b6093568f930c3a5828af71bc37d631d95252ae3ea88201131e8587be7b668d824daecd0310131652507bab67b4d135917575e8ab9ab911a1cc59b342dfe3815a4b49f10f506577778bc3e8cd98dcfe0f8ba24f114a6f';

const defaultConfig: configType = {
  concurrency: 4,
  saveLayout: {
    track: 'Music/{ALB_TITLE}/{SNG_TITLE}',
    album: 'Music/{ALB_TITLE}/{SNG_TITLE}',
    artist: 'Music/{ALB_TITLE}/{SNG_TITLE}',
    playlist: 'Playlist/{TITLE}/{SNG_TITLE}',
  },
  playlist: {
    resolveFullPath: false,
  },
  trackNumber: true,
  fallbackTrack: true,
  fallbackQuality: true,
  coverSize: {
    '128': 500,
    '320': 500,
    flac: 1000,
  },
  cookies: {
    arl: 'c911a4ac9f44a52bf23720cc88588557d999b975094068d258e617bf3e9110a2626c2ff7f5d3cb471b435512e0f5a4de4d7d7e3becad4bf80b0a0e230d9001a814124f87833fe772fb6b1327d2be740f65bc5bcfc1de9171926b5ea9aae69db7',
  },
};

class Config {
  public userConfigLocation: string | null;
  private configFile: string;
  private store: configType;

  constructor(configFile = 'd-fi.config.json') {
    this.userConfigLocation = null;
    this.configFile = configFile;
    this.store = this.getConfig(configFile);

    // migrate data
    if (this.store.cookies.arl === old_arl) {
      this.set('cookies.arl', defaultConfig.cookies.arl);
    }
  }

  private getConfig(configFile: string): configType {
    if (!existsSync(configFile)) {
      return defaultConfig;
    }

    try {
      const userConfig: configType = JSON.parse(readFileSync(configFile, 'utf-8'));
      if (userConfig.saveLayout) {
        userConfig.saveLayout = {...defaultConfig.saveLayout, ...userConfig.saveLayout};
      }
      if (userConfig.playlist) {
        userConfig.playlist = {...defaultConfig.playlist, ...userConfig.playlist};
      }
      if (userConfig.coverSize) {
        userConfig.coverSize = {...defaultConfig.coverSize, ...userConfig.coverSize};
      }
      if (userConfig.cookies) {
        userConfig.cookies = {...defaultConfig.cookies, ...userConfig.cookies};
      }
      this.userConfigLocation = configFile;
      return {...defaultConfig, ...userConfig};
    } catch (err: any) {
      console.error(signale.error(`Unable to parse config: ${configFile}`));
      console.error(signale.note(err.message));
      console.warn(signale.warn('Falling back to default config'));
      return defaultConfig;
    }
  }

  /**
   * Get an item.
   * @param key - The key of the item to get.
   * @param defaultValue - The default value if the item does not exist.
   */
  get(key: keysType, defaultValue?: string | boolean | number) {
    return dotProp.get(this.store, key, defaultValue);
  }

  /**
   * Set an item or multiple items at once.
   * @param {key|object} - You can use [dot-notation](https://github.com/sindresorhus/dot-prop) in a key to access nested properties. Or a hashmap of items to set at once.
   * @param value - Must be JSON serializable. Trying to set the type `undefined`, `function`, or `symbol` will result in a `TypeError`.
   */
  set(key: keysType, value: string | boolean | number, persist = true) {
    dotProp.set(this.store, key, value);
    if (persist) {
      writeFileSync(this.configFile, JSON.stringify(this.store, null, 2));
    }
    return this.configFile;
  }

  /**
   * Delete an item.
   * @param key - The key of the item to delete.
   */
  delete(key: keysType) {
    dotProp.delete(this.store, key);
  }
}

export default Config;
